package com.meituan.sqt.utils;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Author: chenzhenfeng@meituan.com
 * Date: 18/3/8 下午12:26
 * Description:
 */
public class HttpClientUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtil.class);

    private static PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager();
    private static CloseableHttpClient httpClient = null;
    private static CloseableHttpClient httpsClient = null;
    private static HttpClientUtil.HttpConfig httpConfig = null;
    private static SSLConnectionSocketFactory sslsf = null;
    // 连接池最大连接数和每路由最大连接数可通过sqt-httpconf.properties进行配置
    private static final String HTTP_CONF_FILE_NAME = "sqt-httpconf.properties";
    private static final int MAX_TOTAL_CONNECTION = 800;
    private static final int MAX_PER_ROUTE = 150;
    private static String UTF_8 = "UTF-8";

    interface ContentType {
        String FORM_URLENCODED = "application/x-www-form-urlencoded";
        String JSON = "application/json; charset=utf-8";
    }

    interface AcceptType {
        String ACCEPT_JSON = "application/json";
        String ACCEPT_ANNY = "*/*";
    }

    private HttpClientUtil() {
        // prevent instantialization
    }

    static {
        loadConf();
        httpClientConnectionManager.setMaxTotal(httpConfig.getMaxTotalConnection());  // 连接池最大连接数
        httpClientConnectionManager.setDefaultMaxPerRoute(httpConfig.getMaxPerRoute());  // 每路由最大连接数

        // 通过连接池获取的httpClient
        httpClient = HttpClients.custom().setConnectionManager(httpClientConnectionManager).build();
        // 通过连接池获取的httpsClient, 能支持https
        httpsClient = HttpClients.custom().setSSLSocketFactory(sslsf).setConnectionManager(httpClientConnectionManager).build();
        LOGGER.info("HttpClient initialization");
    }

    /**
     * 如果配置httpconf.properties, 则从该文件中读取http连接池的配置参数, 否则使用默认值
     */
    private static void loadConf() {
        if (httpConfig == null) {
            httpConfig = new HttpConfig();
        }
        Properties properties = new Properties();
        try {
            InputStream inputStream = HttpClientUtil.class.getClassLoader().getResourceAsStream(HTTP_CONF_FILE_NAME);

            if (inputStream == null) {
                LOGGER.warn("httpConfig file={} does not exist", HTTP_CONF_FILE_NAME);
                return;
            }
            properties.load(inputStream);
            int maxTotalConnection = Integer.parseInt(properties.getProperty("max_total_connection"));
            int maxPerRoute = Integer.parseInt(properties.getProperty("max_per_route"));
            LOGGER.info("max_total_connection={}, max_per_route={}", maxTotalConnection, maxPerRoute);
            httpConfig.setMaxTotalConnection(maxTotalConnection);
            httpConfig.setMaxPerRoute(maxPerRoute);
        } catch (IOException e) {
            LOGGER.warn("read httpConfig from file={} failed", HTTP_CONF_FILE_NAME, e);
        } catch (NumberFormatException e) {
            LOGGER.warn("read httpConfig from file={} failed", HTTP_CONF_FILE_NAME, e);
        } catch (Exception e) {
            LOGGER.warn("read httpConfig from file={} failed", HTTP_CONF_FILE_NAME, e);
        }
    }

    private static void initHttps() {
        X509TrustManager trustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        try {
            SSLContext sslContext = SSLContext.getInstance("TLS");
            // 初始化SSL上下文
            sslContext.init(null, new TrustManager[]{trustManager}, null);
            // SSL套接字连接工厂,NoopHostnameVerifier为信任所有服务器
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
        } catch (NoSuchAlgorithmException e) {
            LOGGER.error("初始化https支持失败", e);
        } catch (KeyManagementException e) {
            LOGGER.error("初始化https支持失败", e);
        }
    }

    static class HttpConfig {
        private int maxTotalConnection = MAX_TOTAL_CONNECTION;
        private int maxPerRoute = MAX_PER_ROUTE;

        public int getMaxTotalConnection() {
            return maxTotalConnection;
        }

        public void setMaxTotalConnection(int maxTotalConnection) {
            this.maxTotalConnection = maxTotalConnection;
        }

        public int getMaxPerRoute() {
            return maxPerRoute;
        }

        public void setMaxPerRoute(int maxPerRoute) {
            this.maxPerRoute = maxPerRoute;
        }
    }

    public static String invokePost(String url, Map<String, String> headers, Map<String, String> params) throws Exception {
        URIBuilder uriBuilder = new URIBuilder();
        valueForUriBuilder(url, uriBuilder);

        HttpPost httpPost = new HttpPost(uriBuilder.build());

        if (headers != null && headers.size() > 0) {
            for (Map.Entry<String, String> param : headers.entrySet()) {
                httpPost.addHeader(param.getKey(), param.getValue());
            }
        }

        if (params != null && params.size() > 0) {
            List<NameValuePair> pairs = new ArrayList<NameValuePair>();
            for (Map.Entry<String, String> param : params.entrySet()) {
                pairs.add(new BasicNameValuePair(param.getKey(), param.getValue()));
            }
            httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));
        }

        return sendRequest(url, httpPost);
    }


    public static String invokePost(String url, Map<String, String> headers, String body) throws Exception {
        URIBuilder uriBuilder = new URIBuilder();
        valueForUriBuilder(url, uriBuilder);

        HttpPost httpPost = new HttpPost(uriBuilder.build());

        if (headers != null && headers.size() > 0) {
            for (Map.Entry<String, String> param : headers.entrySet()) {
                httpPost.addHeader(param.getKey(), param.getValue());
            }
        }

        if (body != null) {
            httpPost.setEntity(new StringEntity(body, Charset.forName(UTF_8)));
        }

        return sendRequest(url, httpPost);
    }

    public static String invokePost(String url, String body, String contentType, String accept) throws Exception {
        URIBuilder uriBuilder = new URIBuilder();
        valueForUriBuilder(url, uriBuilder);

        HttpPost httpPost = new HttpPost(uriBuilder.build());

        httpPost.addHeader("Content-type",contentType);
        httpPost.setHeader("Accept", accept);
        httpPost.setEntity(new StringEntity(body, Charset.forName(UTF_8)));

        return sendRequest(url, httpPost);
    }

    public static String invokeGet(String url) throws Exception {
        // 原始访问url
        HttpGet httpGet = new HttpGet(url);

        return sendRequest(url, httpGet);
    }


    private static String sendRequest(String url, HttpRequestBase request) throws Exception {
        CloseableHttpClient client;
        if (url.startsWith("https")) {
            client = httpsClient;
        } else {
            client = httpClient;
        }
        long st = System.currentTimeMillis();
        String responseStr;
        int status = 0;
        try {
            CloseableHttpResponse response = client.execute(request);
            HttpEntity entity = response.getEntity();
            status = response.getStatusLine().getStatusCode();
            if (entity != null) {
                responseStr = EntityUtils.toString(entity, UTF_8);
                response.close();
            } else {
                responseStr = StringUtils.EMPTY;
            }
        } catch (SocketTimeoutException e) {
            LOGGER.error("HttpClient.sendRequest, url:{}, use_time:{} ms",
                    request.getURI(), (System.currentTimeMillis() - st), e);
            throw e;
        } catch (ClientProtocolException e) {
            LOGGER.error("HttpClient.sendRequest, url:{}, use_time:{} ms",
                    request.getURI(), (System.currentTimeMillis() - st), e);
            throw e;
        } catch (IOException e) {
            LOGGER.error("HttpClient.sendRequest, url:{}, use_time:{} ms",
                    request.getURI(), (System.currentTimeMillis() - st), e);
            throw e;
        } finally {
            request.releaseConnection();
        }
        LOGGER.info("HttpClient Success, status:{}, url:{}, use_time:{} ms",
                status, request.getURI(), (System.currentTimeMillis() - st));
        return responseStr;
    }

    private static RequestConfig buildRequestConfig(Integer connTimeout, Integer readTimeout) {
        RequestConfig.Builder customReqConf = RequestConfig.custom();
        if (connTimeout != null) {
            customReqConf.setConnectTimeout(connTimeout);
        }
        if (readTimeout != null) {
            customReqConf.setSocketTimeout(connTimeout);
        }
        return customReqConf.build();
    }

    private static void valueForUriBuilder(String url, URIBuilder uriBuilder) {
        Integer apartIndex = url.indexOf("?");
        if (apartIndex == -1) {
            uriBuilder.setPath(url);
        } else {
            uriBuilder.setPath(url.substring(0, apartIndex));
            uriBuilder.setCustomQuery(url.substring(apartIndex + 1, url.length()));
        }
    }
}
